//$Id: GameState.java 227 2010-11-07 23:41:05Z cr.carbon $

package builders;

import java.util.ArrayList;

/**
 * This is the class for keeping track of the state of the game
 * 
 * @author Yuling
 * 
 */
public class GameState {

	/**
	 * The GameMode is responsible for keeping current state of game, and facilitating the flow of states (as seen in the Message class)
	 */
	public enum GameMode {
		SPLASH_SCREEN, NEW_GAME, INITIALIZE, ROLL_DIE, SET_DIE_ROLL, MOVE_PIECE, TILE_RESULTS, END_TURN, BUILD, END_GAME, LOAD_GAME, SAVE_GAME, RESUME, ERROR
	}
	
	public enum AIDifficulty {
		EASY, NORMAL, HARD
	}

	private GameMode gameMode = GameMode.SPLASH_SCREEN;
	private GameMode prevGameMode;
	private String output;
	private String prevOutput;
	private AIDifficulty difficultyLevel;
	private ArrayList<Player> players = new ArrayList<Player>();
	private Player currentPlayer;
	private final int WINNING_CONDITION = 20;
	private final int MAX_BUILDINGS = 8;
	private ArrayList<Integer> tilesYouCanMoveTo = new ArrayList<Integer>();
	private int movementActivityPoints;
	private TileManager tileManager = new TileManager();
	private BuildingManager buildingManager = new BuildingManager();
	private ArrayList<Building> buildingsYouCanBuild = new ArrayList<Building>();
	private ArrayList<Building> allBuildingsAvailableForBuilding = new ArrayList<Building>();

	/**
	 * This method returns the currentPlayer of the game, null if no such Player exists
	 * @return the CurrentPlayer of the game
	 */
	public Player getCurrentPlayer(){ 
		if (currentPlayer == null){ // if the currentPlayer is not known, the game has just started and the currentPlayer has not been initialised yet
			if (players.isEmpty()){ // but if you haven't added players, cannot get a currentPlayer
				return null; // hence, return null
			} else currentPlayer = players.get(0); // otherwise return the first player
		}
		return currentPlayer;
	}

	/**
	 * This method allows for players to be added to the game
	 * @param player
	 */
	public void addPlayer(Player player){
		players.add(player);
	}

	/**
	 * Private method for accessing the Players in the ArrayList<Player> players
	 * @param i
	 * @return
	 */
	private Player getPlayer(int i){
		return players.get(i);
	}
	
	/**
	 * for internal implementation only, and to facilitate getNextPlayer
	 * @param player
	 */
	private void setCurrentPlayer(Player player){
		currentPlayer = player;
	}
	
	/**
	 * For Logic to call when games are being loaded
	 * @param playerNumber the playerNumber that should be set as the Player
	 */
	public void setCurrentPlayer (int playerNumber){
		if (playerNumber >= 0 && playerNumber < players.size()){
			currentPlayer = players.get(playerNumber);
		}
	}

	/**
	 * Internal helper method for setting next Player
	 * @return
	 */
	private Player getNextPlayerAfterCurrentPlayer(){
		// iterate through the list to get the next player
		for (int i = 0; i < players.size(); i++){
			if (getPlayer(i).equals(currentPlayer)){
				return getPlayer((i + 1) % players.size());
			}
		}
		return null;
	}

	/**
	 * For Logic to call when it is the end of the players' turn
	 * Will set the Next Player to be the currentPlayer
	 */
	public void setNextPlayer() {
		setCurrentPlayer(getNextPlayerAfterCurrentPlayer());
	}

	public boolean lastPlayer() {
		// if the next player is the first player
		if (getNextPlayerAfterCurrentPlayer().equals(players.get(0))){
			return true;
		}
		return false;
	}

	/**
	 * For Logic to save game (in case two players have the same name, we identify the currentPlayer by his number in the save game file)
	 * @param player
	 * @return   the number of the Player
	 */
	public Integer getPlayerNumber(Player player) {
		if (players.contains(player)){
			int i = 0;
			for (; i < players.size() ; i++){
				if (players.get(i).equals(player)){
					break;
				}
			}
			return i;
		} else return null;
	}

	/**
	 * for UI to refer to when necessary
	 * @return the list of players
	 */
	public ArrayList<Player> getPlayers(){
		return players;
	}
	
	/**
	 * Logic to load the players into gameState by calling this method
	 * @param players
	 */
	public void setPlayers(ArrayList<Player> players){
		this.players = players;
	}

	/**
	 * Logic to facilitate flow of game by using this
	 * @param gameMode the GameMode that GameState should flow into
	 */
	public void setGameMode(GameMode gameMode) {
		this.gameMode = gameMode;
	}

	/**
	 * Logic to refer to this when checking flow of game, UIs to use this method when building their own interface for players
	 * @return
	 */
	public GameMode getGameMode() {
		return gameMode;
	}

	/**
	 * Logic to call this in order to preserve a certain state
	 * @param prevGameMode
	 */
	public void setPrevGameMode(GameMode prevGameMode) {
		this.prevGameMode = prevGameMode;
	}

	/**
	 * To find out prev GameMode of game (especially when ERRORs are found, or when SAVE_GAME, LOAD_GAME, INITALIZE are called)
	 * @return
	 */
	public GameMode getPrevGameMode() {
		return prevGameMode;
	}

	/**
	 * Logic to use this message to set any messages that it wants UI to output
	 * @param output
	 */
	public void setOutput(String output) {
		this.output = output;
	}

	/**
	 * UIs to call this to get the output message
	 * @return
	 */
	public String getOutput() {
		return output;
	}

	/**
	 * For keeping track of previous output, just like in setPrevGameMode()
	 * @param prevOutput
	 */
	public void setPrevOutput(String prevOutput) {
		this.prevOutput = prevOutput;
	}

	/**
	 * Getter of prevOutput, in order to put it back into Output
	 * @return the previous output
	 */
	public String getPrevOutput() {
		return prevOutput;
	}

	/**
	 * For Logic to set so that Gamestate knows what tiles the Player can move to
	 * @param tilesYouCanMoveTo
	 */
	public void setTilesYouCanMoveTo(ArrayList<Integer> tilesYouCanMoveTo) {
		this.tilesYouCanMoveTo = tilesYouCanMoveTo;
	}

	/**
	 * For UIs to refer to (and potentially check)
	 * @return an ArrayList<Integer> of tileNumbers you can move to
	 */
	public ArrayList<Integer> getTilesYouCanMoveTo() {
		return tilesYouCanMoveTo;
	}

	/**
	 * sets the MovementActivityPoints of the Player based on Die Roll
	 * @param movementActivityPoints
	 */
	public void setMovementActivityPoints(int movementActivityPoints) {
		this.movementActivityPoints = movementActivityPoints;
	}

	/**
	 * For Logic to call
	 * Will minus off movement points from that, so that Logic can find out the activity points of that Player
	 * @return
	 */
	public int getMovementActivityPoints() {
		return movementActivityPoints;
	}

	/**
	 * Setting of tileManager
	 * @param tileManager
	 */
	public void setTileManager(TileManager tileManager) {
		this.tileManager = tileManager;
	}

	/**
	 * Getting of tileManager (esp. for GUI)
	 * @return
	 */
	public TileManager getTileManager() {
		return tileManager;
	}

	/**
	 * Setting of buildingManager
	 * @param buildingManager
	 */
	public void setBuildingManager(BuildingManager buildingManager) {
		this.buildingManager = buildingManager;
	}

	/**
	 * Getting of buildingManager
	 * @return
	 */
	public BuildingManager getBuildingManager() {
		return buildingManager;
	}

	/**
	 * Setting of BuildingsYouCanBuild (Logic to make an Array of such Buildings before setting it; to be used in Build GameMode)
	 * @param buildingsYouCanBuild
	 */
	public void setBuildingsYouCanBuild(ArrayList<Building> buildingsYouCanBuild) {
		this.buildingsYouCanBuild = buildingsYouCanBuild;
	}

	/**
	 * For error checking as well as for UIs to display
	 * @return
	 */
	public ArrayList<Building> getBuildingsYouCanBuild() {
		return buildingsYouCanBuild;
	}

	/**
	 * Setting of allBuildingsAvailableForBuilding
	 * @param allBuildingsAvailableForBuilding
	 */
	public void setAllBuildingsAvailableForBuilding(ArrayList<Building> allBuildingsAvailableForBuilding) {
		this.allBuildingsAvailableForBuilding = allBuildingsAvailableForBuilding;
	}

	/**
	 * Getting of allBuildingsAvailableForBuilding
	 * @return
	 */
	public ArrayList<Building> getAllBuildingsAvailableForBuilding() {
		return allBuildingsAvailableForBuilding;
	}	

	/**
	 * To find out if someone has either accumulated enough points, or built enough buildings
	 * @return true if there is a winner and false otherwise
	 */
	public boolean hasWon() {
		boolean hasWon = false;
		for (Player p : players){
			if (p.getPointsAccumulated() >= WINNING_CONDITION || p.getBuildingsOwned().size() >= MAX_BUILDINGS){
				hasWon = true;
			}
		}
		return hasWon;
	}

	/**
	 * Gets the list of winners (a list in case of a tie)
	 * @return
	 */
	public ArrayList<Player> getWinners() {
		if (players != null){
			int points = 0;
			for (Player p : players){
				// find the player with the maximum points
				if (points < p.getPointsAccumulated()){
					points = p.getPointsAccumulated();
				} 
			}
			ArrayList<Player> winnerList = new ArrayList<Player>();
			for (Player p : players){
				if (p.getPointsAccumulated() == points){
					winnerList.add(p);
				}
			}
			return winnerList;
		}
		else return null;
	}

	/**
	 * To facilitate new Game mode and remove potential abuses of system
	 * @param listToReset
	 */
	public void resetThings(ArrayList<Boolean> listToReset) {
		if (listToReset.size() == 13){
			if (listToReset.get(0)){
				this.gameMode = GameMode.SPLASH_SCREEN;
			}
			if (listToReset.get(1)){
				this.prevGameMode = null;
			}
			if (listToReset.get(2)){
				this.output = null;
			}
			if (listToReset.get(3)){
				this.prevOutput = null;
			}
			if (listToReset.get(4)){
				this.difficultyLevel = null;
			}
			if (listToReset.get(5)){
				this.players = new ArrayList<Player>();
			}
			if (listToReset.get(6)){
				this.currentPlayer = null;
			}
			if (listToReset.get(7)){
				this.tilesYouCanMoveTo = new ArrayList<Integer>();
			}
			if (listToReset.get(8)){
				this.movementActivityPoints = 0;
			}
			if (listToReset.get(9)){
				this.tileManager = new TileManager();
			}
			if (listToReset.get(10)){
				this.buildingManager = new BuildingManager();
			}
			if (listToReset.get(11)){
				this.buildingsYouCanBuild = new ArrayList<Building>();
			}
			/*if (listToReset.get(12)){
				this.allBuildingsAvailableForBuilding = new ArrayList<Building>();
			}*/ //don't reset this
		}
	}

	/**
	 * For Logic to call whenever there is a new game. "returns" a totally new gameState
	 */
	public void resetEverything() {
		ArrayList<Boolean> thingsToReset = new ArrayList<Boolean>();
		for (int i = 0; i < 13 ; i++){
			thingsToReset.add(true);
		}
		resetThings(thingsToReset);
	}

	public int getWinningCondition(){
		return WINNING_CONDITION;
	}
	
	/**
	 * Setter of difficulty level
	 * @param difficultyLevel
	 */
	public void setDifficultyLevel(AIDifficulty difficultyLevel) {
		this.difficultyLevel = difficultyLevel;
	}

	/**
	 * Getter of difficulty level
	 * @return
	 */
	public AIDifficulty getDifficultyLevel() {
		return difficultyLevel;
	}
	
}
